<?php

/*

Copyright (c) 2010 four.zero.one.unauthorized@gmail.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Author: four.zero.one.unauthorized@gmail.com
Initial Build Date: 7/29/2010
Rev Date: 8/3/2010 01:00
Summary: ROUGH DRAFT PHP ScriptControl Wrapper.  Allow mixing PHP script with VBScript and JScript.

Extensions Used:
-none-

Dependancies:
1) PHP 5
2) WIN
3) Com class - "MSScriptControl.ScriptControl"
4) attached \variant_lib.php CSTM_variant_get_php_value() function

References:
1) http://msdn.microsoft.com/en-us/library/aa227633%28v=VS.60%29.aspx
2) http://msdn.microsoft.com/en-us/library/hbxc2t98%28v=VS.85%29.aspx
3) http://msdn.microsoft.com/en-us/library/t0aew7h6%28VS.85%29.aspx
4) http://code.google.com/p/corporate-overload/wiki/PHP_MS_Script_Wrapper

Warnings:
1) Each newly created object has virtually its own global scope.  Code declared in one object
will not be available in another.  Up to the user to find out how this is useful.
2) Meant for single-client, PHP command line interface only.

*/

require_once 'variant_lib.php';

class Script_Core {
	private $COM;
	private $COM_Defaults = array(
		'Timeout' => 30000,
		'AllowUI' => true,
		'Language' => 'VBScript'
		);
	private $CSTM_Is_Scripting = false;
	const S_LANGUAGE_VB = 'VBScript';
	const S_LANGUAGE_JS = 'JScript';
	const S_PROCEDURE_VB_SUB = 'Sub';
	const S_PROCEDURE_VB_FUNC = 'Function';
	const S_PROCEDURE_J_FUNC = 'function';

	function __construct(/* (string) VBScript||JScript */ $type = null)
		{
		if ($type !== null AND !(is_string($type) AND 
			($type === self::S_LANGUAGE_VB || $type === self::S_LANGUAGE_JS))) 
			{
			throw new Exception("Unknown scripting language. ".self::S_LANGUAGE_VB." or "
				.self::S_LANGUAGE_JS." only!");
			}
		$this->COM = new COM("MSScriptControl.ScriptControl");
		foreach ($this->COM_Defaults as $prop => $var) {
			if ($prop === 'Language') {
				$this->COM->$prop = $type ?: $var;
				}
			$this->COM->$prop = $this->$prop;
			}
		}

	function __get(/* (string) */ $prop)
		/* Return live ScriptControl object values only. */
		{
		return $this->COM->$prop;
		}

	function __set(/* (string) */ $prop, /* (mix) */ $val) 
		/* Set live ScriptControl object values only. */
		{
		$this->COM->$prop = $val;
		}

	function __call($fnc, $args) 
		/* Misc pre-defined functions */
		{
		switch ((string) $fnc) {
			case "AddCode":
				/*
					Add to and execute code in the global module.
					The behavior of 'AddCode' in scriptcontrol is to execute code outside of subs or
					functions, yet make newly defined subs and functions available to later Evil and
					ExecuteSatement calls. Haven't decided if that is a problem yet.
				*/
			case "Eval":
				/*
					Can't name the method 'Eval' as that name is already taken.
					Evaluate an expression within the context of the global scriptcontrol module.
					Remember that VBScript statements -must- wrap their parameter list in () when 
					used in an expression.
				*/
			case "ExecuteStatement":
				/*
					Execute a statement within the context of the global scriptcontrol module.
					Remember that VBScript statements must -not- wrap their parameter list in () 
					when used in a statement.
				*/
				if (count($args) !== 1 OR !is_string($args[0])) {
					throw new Exception("Expression or Statement is not a string!");
					}
				$args[0] = trim($args[0]);
				return call_user_func(array($this->COM, $fnc), $args[0]);
			case "AddObject":
				/*
					(string) $Name, (VT_DISPATCH [9]) $Object, (bool) $AddMembers
					Add an object to the global namespace of the scripting engine.
					Don't know what this does yet.
				*/
				if (count($args) < 3) {
					throw new Exception("Parameters missing!");
					}
				if (!is_string($args[0])) {
					throw new Exception("Name is not a string!");
					}
				return call_user_func_array(array($this->COM, $fnc), $args);
			case "AboutBox":
				/* show script control about popup */
			case "Reset":
			default:
				return call_user_func(array($this->COM, $fnc));
			}
		return call_user_func_array(array($this->COM, $fnc), $args);
		}

	function Run(/* string */ $ProcedureName = null)
		/*
			Call a named procedure ("Sub") or function defined in the global scriptcontrol module.
			If $ProcedureName is null, return CodeObject and pass on handling like a defined method 
			of that object instead.
			Otherwise, Variable length argument list. Variable list must match list expected by 
			called procedure or else an "unknown" error will occur. Return numbered array of 
			original parameters altered by procedure by order of input.  If a single argument 
			besides $ProcedureName is given, it will return the altered argument only.
		*/
		{
		if ($ProcedureName === null) {
			return $this->COM->CodeObject;
			}
		if (!is_string($ProcedureName)) {
			throw new Exception("ProcedureName is not a string!");
			}
		$arg_array = func_get_args();
		array_walk_recursive($arg_array, function(&$arg, $key) {
			if ($key === 0) {return;}
			$arg = new VARIANT($arg);
			});
		call_user_func_array(array($this->COM, 'Run'), $arg_array);
		array_shift($arg_array);
		if (count($arg_array)) {return $arg_array[0];}
		return $arg_array;
		}

	function create_procedure(/* (string) */ $type, /* (string) */ $name, /* (string) */ $args, 
			/* (string) */ $block)
		/* 
			Same idea as normal create_function php function.
			$Type can use S_PROCEDURE_VB_SUB, S_PROCEDURE_VB_FUNC, S_PROCEDURE_J_FUNC class constant
			values.
			$Name is function or sub name
			$Args is comma-separated list of variable names.
			$Block is the block of code as the meat of the function or sub.
		*/
		{
		if (!is_string($type) OR ($type !== self::S_PROCEDURE_VB_SUB AND 
				$type !== self::S_PROCEDURE_VB_FUNC AND $type !== self::S_PROCEDURE_J_FUNC))
			{
			throw new Exception("Type is not defined!");
			}
		if (!is_string($name)) {
			throw new Exception("name is not a string!");
			}
		if (!is_string($block)) {
			throw new Exception("block is not a string!");
			}
		if ((string) $this->COM->Language === self::S_LANGUAGE_VB) {
			$fnc_header = $type.' '.$name."($args)\n";
			$fnc_footer = "End $type";
			}
		else if ((string) $this->COM->Language === self::S_LANGUAGE_JS) {
			$fnc_header = $type.' '.$name."($args) {\n";
			$fnc_footer = "}";
			}
		$fnc_block = trim($block)."\n";
		$this->COM->AddCode($fnc_header.$fnc_block.$fnc_footer);
		}
		
	function Script_Start()
		/*
			Virtually opening a script tag.  Any outputted text beyond this point will be handled by
			scriptcontrol. Recommend using ?> PHP close tag at this point and just write script of 
			chosen language for better readability.
			If using more ob functions in between, make sure you un-nest them before calling 
			Script_End()
		*/
		{
		ob_start();
		$this->CSTM_Is_Scripting = ob_get_level();
		}

	function Script_End()
		/*
			Virtually closing a script tag.  Any outputted text above to where Script_Start() was 
			called will be handled by scriptcontrol.
			Use <?php PHP open tag before this function if not currently within PHP scope.
		*/
		{
		if (!$this->CSTM_Is_Scripting || ob_get_level() !== $this->CSTM_Is_Scripting) {
			throw new Exception("End function without Start function!");
			}
		$text = trim(ob_get_contents());
		ob_end_clean();
		$this->CSTM_Is_Scripting = false;
		$this->COM->AddCode($text);
		}
		
	function Procedures()
		/* Returns Procedure property of scripting object as array */
		{
		$return = array();
		foreach ($this->COM->Procedures as $proc) {
			$return[(string) $proc] = array(
				"arg_count" => $proc->NumArgs, 
				"is_function" => $proc->HasReturnValue
				);
			}
		return $return;
		}
	
	function Error()
		/*
			Outputs (VT_DISPATCH [9]) Error Object...
			$Number: Error number
			$Source: Source of the error
			$Description: Friendly description of error
			$HelpFile: File in which help for the error can be found
			$HelpContext: Context ID for the topic with information on the error
			$Text: Line of source code on which the error occu
			$Line: Source code line number where the error occurred
			$Column: Source code column position where the error occurred
			Clear(): Clear the script error
		*/
		{
		return $this->COM->Error;
		}
		
	}

	
?>